/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.engine.runtime.jasper.ds;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.EntityNotFoundException;
import cn.easyplatform.ScriptEvalExitException;
import cn.easyplatform.ScriptRuntimeException;
import cn.easyplatform.contexts.Contexts;
import cn.easyplatform.contexts.RecordContext;
import cn.easyplatform.contexts.WorkflowContext;
import cn.easyplatform.dao.BizDao;
import cn.easyplatform.dao.utils.SqlUtils;
import cn.easyplatform.dos.FieldDo;
import cn.easyplatform.dos.Record;
import cn.easyplatform.entities.beans.LogicBean;
import cn.easyplatform.entities.beans.report.JasperReportBean;
import cn.easyplatform.i18n.I18N;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.spi.listener.event.MessageEvent;
import cn.easyplatform.messages.vos.executor.BeginVo;
import cn.easyplatform.messages.vos.executor.ProgressVo;
import cn.easyplatform.support.Closeable;
import cn.easyplatform.support.report.GroupCallback;
import cn.easyplatform.support.scripting.CompliableScriptEngine;
import cn.easyplatform.support.scripting.ScriptEngineFactory;
import cn.easyplatform.support.sql.SqlParser;
import cn.easyplatform.type.EntityType;
import cn.easyplatform.util.RuntimeUtils;
import net.sf.jasperreports.engine.JRException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class QueryDataSource extends AbstractDataSource implements Closeable,
        GroupCallback {

    private final static Logger log = LoggerFactory.getLogger(QueryDataSource.class);

    private CommandContext cc;

    private BizDao dao;

    private ResultSet rs;

    private ResultSetMetaData rsmd;

    private WorkflowContext ctx;

    private boolean isSubReport;

    private JasperReportBean rb;

    private int rowIndex;

    private CompliableScriptEngine engine;

    private CompliableScriptEngine groupEngine;

    private RecordContext prev;

    public QueryDataSource(CommandContext cc, JasperReportBean rb,
                           boolean isSubReport) {
        this.cc = cc;
        this.rb = rb;
        this.ctx = this.cc.getWorkflowContext();
        this.isSubReport = isSubReport;
        if (!isSubReport) {
            Contexts.set(RecordContext.class, ctx.getRecord());
            try {
                init();
            } catch (SQLException ex) {
                if (log.isErrorEnabled())
                    log.error("executeQuery", ex);
                throw new EasyPlatformWithLabelKeyException(
                        "dao.biz.query.error", ex, rb.getQuery());
            }
        }
        if (rb.getOnRecord() != null) {
            String content = null;
            if (!Strings.isBlank(rb.getOnRecord().getId())) {
                LogicBean lb = cc.getEntity(rb.getOnRecord().getId());
                if (lb == null)
                    throw new EntityNotFoundException(
                            EntityType.LOGIC.getName(), rb.getOnRecord()
                            .getId());
                content = lb.getContent().trim();
            } else if (!Strings.isBlank(rb.getOnRecord().getContent()))
                content = rb.getOnRecord().getContent().trim();
            if (!Strings.isBlank(content))
                engine = ScriptEngineFactory
                        .createCompilableEngine(cc, content);
        }
        if (rb.getOnGroup() != null) {
            String content = null;
            if (!Strings.isBlank(rb.getOnGroup().getId())) {
                LogicBean lb = cc.getEntity(rb.getOnGroup().getId());
                if (lb == null)
                    throw new EntityNotFoundException(
                            EntityType.LOGIC.getName(), rb.getOnRecord()
                            .getId());
                content = lb.getContent().trim();
            } else if (!Strings.isBlank(rb.getOnGroup().getContent()))
                content = rb.getOnGroup().getContent().trim();
            if (!Strings.isBlank(content))
                groupEngine = ScriptEngineFactory.createCompilableEngine(cc,
                        content);
        }
    }

    private void init() throws SQLException {
        current = Contexts.get(RecordContext.class);
        SqlParser<FieldDo> sp = RuntimeUtils.createSqlParser(FieldDo.class);
        String sql = sp.parse(rb.getQuery(), current);
        dao = cc.getBizDao(rb.getDsId());
        if (!isSubReport) {
            int count = dao.getCount(sql, sp.getParams());
            BeginVo bv = new BeginVo((I18N.getLabel("report.execute.begin",
                    rb.getName())));
            bv.setTotal(count);
            cc.send(new MessageEvent(rb.getId(), bv));
        }
        if (log.isDebugEnabled())
            log.debug("execute report-> {},{}", sql, sp.getParams());
        StringBuilder sb = new StringBuilder(sql);
        if (!Strings.isBlank(rb.getGroupBy()))
            sb.append(" group by ").append(rb.getGroupBy());
        if (!Strings.isBlank(rb.getOrderBy()))
            sb.append(" order by ").append(rb.getOrderBy());
        rs = dao.executeQuery(sb.toString(), sp.getParams());
        rsmd = rs.getMetaData();
    }

    @Override
    public boolean next() throws JRException {
        try {
            if (isSubReport && rs == null)
                init();
            boolean hasNext = rs.next();
            if (hasNext) {
                rowIndex++;
                int size = rsmd.getColumnCount();
                Record record = new Record();
                for (int index = 1; index <= size; index++)
                    record.set(SqlUtils.getValue(rs, rsmd, index));
                current.setData(record);
                if (prev == null)
                    prev = current;
                Contexts.set(RecordContext.class, current);
                if (!isSubReport && rowIndex % 50 == 0)
                    cc.send(new MessageEvent(rb.getId(), new ProgressVo(
                            rowIndex)));
                if (engine != null) {
                    try {
                        engine.eval(current);
                    } catch (ScriptEvalExitException ex) {
                        throw new ScriptRuntimeException(ex.getMessage(),
                                cc.getMessage(ex.getMessage(), ex.getRecord()));
                    }
                }
                prev = current.clone();
            } else if (rs != null) {
                dao.close(rs);
                rs = null;
                dao = null;
                prev = null;
            }
            return hasNext;
        } catch (Exception ex) {
            if (dao != null) {
                dao.close(rs);
                dao = null;
            }
            if (ex instanceof EasyPlatformWithLabelKeyException
                    || ex instanceof ScriptRuntimeException)
                throw (RuntimeException) ex;
            if (log.isErrorEnabled())
                log.error("next", ex);
            throw new JRException(ex);
        }
    }

    @Override
    public void close() {
        if (engine != null)
            engine.destroy();
        if (groupEngine != null)
            groupEngine.destroy();
        if (dao != null) {
            dao.close(rs);
            rs = null;
        }
    }

    @Override
    public void onGroup(String groupName) {
        current.setValue("859", groupName);
        if (groupEngine != null) {
            RecordContext tmp = current;
            current = prev;
            try {
                groupEngine.eval(current);
                current = tmp;
            } catch (ScriptEvalExitException ex) {
                throw new ScriptRuntimeException(ex.getMessage(),
                        cc.getMessage(ex.getMessage(), ex.getRecord()));
            }
        }
    }
}
